package ikeypairs

import (
	"context"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"fmt"
	"strings"

	"gitee.com/djaf/my-certs/app/classes/blobs"
	"gitee.com/djaf/my-certs/app/classes/keypairs"
	"gitee.com/djaf/my-certs/app/data/dxo"
	"gitee.com/djaf/my-certs/app/web/dto"
	"github.com/starter-go/base/lang"
)

// KeyPairServiceImpl ...
type KeyPairServiceImpl struct {

	//starter:component

	_as func(keypairs.Service) //starter:as("#")

	BlobService blobs.Service //starter:inject("#")
	Dao         keypairs.DAO  //starter:inject("#")

}

func (inst *KeyPairServiceImpl) _impl() keypairs.Service {
	return inst
}

// Insert ...
func (inst *KeyPairServiceImpl) Insert(ctx context.Context, item1 *dto.KeyPair) (*dto.KeyPair, error) {

	// save blob

	blob1 := new(dto.BLOB)
	blob1.ContentType = "application/x-security-pem"
	blob1.Label = "key-pair:" + item1.Algorithm
	blob1.Content = item1.PrivateKeyData

	blob2, err := inst.BlobService.Insert(ctx, blob1)
	if err != nil {
		return nil, err
	}

	// save key-pair
	item2, err := keypairs.ConvertD2E(item1)
	if err != nil {
		return nil, err
	}
	item2.Content = blob2.ID
	item3, err := inst.Dao.Insert(nil, item2)
	if err != nil {
		return nil, err
	}
	return keypairs.ConvertE2D(item3)
}

// Update ...
func (inst *KeyPairServiceImpl) Update(ctx context.Context, id dxo.KeyPairID, item *dto.KeyPair) (*dto.KeyPair, error) {
	return nil, fmt.Errorf("no impl")
}

// Remove ...
func (inst *KeyPairServiceImpl) Remove(ctx context.Context, id dxo.KeyPairID) error {
	return fmt.Errorf("no impl")
}

// Find ...
func (inst *KeyPairServiceImpl) Find(ctx context.Context, id dxo.KeyPairID) (*dto.KeyPair, error) {

	kpEntity, err := inst.Dao.Find(nil, id)
	if err != nil {
		return nil, err
	}

	blobID := kpEntity.Content
	blob, err := inst.BlobService.Find(ctx, blobID)
	if err != nil {
		return nil, err
	}

	kpDTO, err := keypairs.ConvertE2D(kpEntity)
	if err != nil {
		return nil, err
	}

	kpDTO.PrivateKeyData = blob.Content
	return kpDTO, nil
}

// List ...
func (inst *KeyPairServiceImpl) List(ctx context.Context) ([]*dto.KeyPair, error) {
	return nil, fmt.Errorf("no impl")
}

// CreateNew ...
func (inst *KeyPairServiceImpl) CreateNew(ctx context.Context, item *dto.KeyPair) (*dto.KeyPair, error) {

	// generate
	pkey, err := rsa.GenerateKey(rand.Reader, 1024*2)
	if err != nil {
		return nil, err
	}

	der := x509.MarshalPKCS1PrivateKey(pkey)

	block := new(pem.Block)
	block.Type = "RSA PRIVATE KEY"
	block.Bytes = der

	output := new(strings.Builder)
	err = pem.Encode(output, block)
	if err != nil {
		return nil, err
	}

	text := output.String()

	// prepare item
	item.Algorithm = "RSA"
	item.Encoding = "DER"
	item.Format = "PEM"
	item.PrivateKeyData = lang.Base64FromBytes([]byte(text))

	// save key-pair
	return inst.Insert(ctx, item)
}

// LoadSigner ...
func (inst *KeyPairServiceImpl) LoadSigner(ctx context.Context, id dxo.KeyPairID) (crypto.Signer, error) {

	kp, err := inst.Find(ctx, id)
	if err != nil {
		return nil, err
	}

	data := kp.PrivateKeyData.Bytes()

	for {
		block, dataRest := pem.Decode(data)
		if block == nil {
			break
		}
		data = dataRest
		bt := strings.ToLower(block.Type)
		a := strings.Contains(bt, "rsa")
		b := strings.Contains(bt, "private")
		c := strings.Contains(bt, "key")
		if a && b && c {
			return inst.loadSignerFromBlock(block)
		}
	}

	return nil, fmt.Errorf("RSA private key not found for the account")
}

func (inst *KeyPairServiceImpl) loadSignerFromBlock(block *pem.Block) (crypto.Signer, error) {
	der := block.Bytes
	pk, err := x509.ParsePKCS1PrivateKey(der)
	if err != nil {
		return nil, err
	}
	return pk, nil
}
